#ifndef INCLUDE_OS_POOL
#define INCLUDE_OS_POOL

#define INITIAL_SIZE 10000

#include <list>
#include <iostream>
#include <cstdlib>
#include <semaphore.h>
#include <pthread.h>

using namespace std;

class node {
	public:
		void * address;
		size_t length;
		bool hole;
};

sem_t sem_name;

class pool {
	public:
		void * memory;
		size_t size;
		list<node*> used; //list of taken and holes
	public:
		pool() {
            sem_init(&sem_name, 0, 1); // Init the semaphore
            sem_wait(&sem_name);

			cout << "== Created a pool. ==" << endl;
			size = INITIAL_SIZE;
			memory = malloc(size);
			node *t = new node;
			t->address = memory;
			t->length = size;
			t->hole = true;
			used.push_back(t);

            sem_post(&sem_name);
		}
		~pool() {
			//delete the nodes one by one
            sem_wait(&sem_name);
			cout << "== Destructor called ==" << endl;
			for (list<node*>::iterator it = used.begin(); it != used.end(); ) {     
				node * cur = *it;
				delete cur;
				it = used.erase(it);
			}
			free((void*)memory);
            sem_post(&sem_name);
            sem_destroy(&sem_name);
		}


		void* requestMem(size_t req_size) {
			cout << "== Requesting space of size: " << req_size << " == " << endl;
            sem_wait(&sem_name);

			for (list<node*>::iterator it = used.begin(); it != used.end(); it++) { 
				node * cur = *it;
				if (cur->hole == true && req_size <= cur->length) {
					//cout << "Found hole of size " << cur->length << endl;
					node *newNode = new node;
					newNode->address = cur->address;
					newNode->length = req_size;
					newNode->hole = false;

					used.insert(it, newNode);
					cur->address = (void*) ((int*)cur->address + req_size);
					cur->length = cur->length - req_size;
					if (cur->length == 0) {
						delete cur;
						used.erase(it);
					}
                    sem_post(&sem_name);
					return newNode->address;
				}
			}
            sem_post(&sem_name);
			return 0;
		}
		bool deleteMem(void* address) {
            sem_wait(&sem_name);
			for (list<node*>::iterator it = used.begin(); it != used.end(); it++) {
				bool nextOrPrev = false;
				node * cur = *it;
				if (cur->address == address) {
					//cout << "Found node." << endl;
					it++;
					node * curNext = *it;

					if (curNext->hole == true) {
						cur->length += curNext->length;
						cur->hole = true;
						delete curNext;
						it = used.erase(it);
						it--;
						//cout << "Combined 2 holes (next)" << endl;
						nextOrPrev = true;
					}
					else {
						it--;
					}

					if (it != used.begin()) {
						it--;
						node * curPrev = *it;
						if (curPrev->hole == true) {
							curPrev->length += cur->length;
							delete cur;
							it++;
							it = used.erase(it);
							//cout << "Combined 2 holes (prev)" << endl;
							nextOrPrev = true;
						}
						else {
							it++;
						}
					}

					if (!nextOrPrev) {
						//cout << "Node is not next or prev. ";
						node * t = *it;
						cout << *((int*)t->address) << ". ";
						t->hole = true;
						//cout << "Made a hole." << endl;
					}
                    sem_post(&sem_name);
					return true;
				}
			}
            sem_post(&sem_name);
			return false;
		}
/*
		void increaseSize() {
			void * oldMemory = memory;
			memory = realloc(memory, size + INCREMENT_SIZE);
			size = size + INCREMENT_SIZE;

			list<node*>::iterator it = used.end();
			it--;
			if ((*it)->hole == true) {
				(*it)->length += INCREMENT_SIZE;
				cout << "Combined with previous hole" << endl;
			}
			else {
				//node *newNode = new node;
				//newNode->address =     ;
				//newNode->length = INCREMENT_SIZE;
				//newNode->hole = true;
				//used.push_back(newNode);
			}
			
			cout << "OLD: " << (int*)oldMemory << " - NEW: " << (int*)memory << " - Difference: " << ((int*)memory - (int*)oldMemory) << endl;
			for (list<node*>::iterator it = used.begin(); it != used.end(); it++) {
				int diff = ((int*)((*it)->address) - (int*)oldMemory);
				(*it)->address = (void*)(diff + (int*)memory);
			}
		}
*/
		bool empty() {
			list<node*>::iterator it = used.begin();
			if (used.size() == 1 && (*it)->hole == true) {
				return true;
			}
			return false;
		}

		size_t length(void *address) {
			for (list<node*>::iterator it = used.begin(); it != used.end(); it++) {
				if ((*it)->address == address && (*it)->hole == false) {
					return (*it)->length;
				}
			}
			return 0;
		}

		void showList() {
			for (list<node*>::iterator it = used.begin(); it != used.end(); it++) {
				node * cur = *it;
				if (cur->hole == true) { cout << "HOLE: length=" << cur->length << endl; }
				else { cout << "NODE: length=" << cur->length << endl; }
			}
		}		
};

/*
 *  We use the following in order to test the pool intsellf. You can try to uncomment it and compile pool.cpp alone to 
 *  see how it works ( do not forget to rename pool.h to pool.cpp )
 *
 
int main() {
	cout << "Hello world" << endl;
	pool * p = new pool();

	int * a = (int*) p->requestMem(sizeof(int));
	int * b = (int*) p->requestMem(sizeof(int));
	int * c = (int*) p->requestMem(sizeof(int));
	int * d = (int*) p->requestMem(sizeof(int));


	cout << &(p->memory) << endl;
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
	cout << d << endl;


	*a = 5;
	*b = 6;
	*c = 7;
	*d = 8;

	cout << *a << endl;

	p->deleteMem(a);
	p->deleteMem(c);

	p->requestMem(sizeof(int));
	//p->deleteMem(d);

	p->showList();



	delete p;
	return 0;
}

*/

#endif
